上一篇文章有提過,相比 localStorage,IndexedDB 的優勢是不限制儲存的資料量,也能處理大量的結構化資料,因此例如圖像儲存、文件管理 ...等等,使用 IndexedDB 也許是個不錯的選擇。
一開始使用時,大家最常碰到的問題就是,這些功能我用 localStorage 處理就好啦,為什麼還要用 IndexedDB?他除了沒有限制儲存資料量之外,還有什麼差別嗎?這邊整理了一些差異,介紹如下:
讓我們試著用 IndexedDB API 做一個簡單的新增 / 刪除筆記功能吧!其實筆記功能也能用 localStorage 做,但可以透過這個範例比較兩者之間的差異。
我的這個筆記功能很簡單,只有一個輸入筆記的框,還有顯示筆記的列表與刪除按鈕,這邊就快速處理 HTML,CSS 不是重點,就不放上來了。
<div class="container">
  <form id="note-form">
    <textarea id="note-content" placeholder="輸入筆記..."></textarea>
    <button type="submit">儲存筆記</button>
  </form>
  <h2>筆記列表</h2>
  <ul id="notes-list"></ul>
</div>
使用 indexedDB.open("notesDB", 1) 開啟 notesDB 的資料庫,並將版號設為 1。特別注意的是,如果資料庫不存在,瀏覽器會自動建立,所以不需額外處理。
let db;
const request = window.indexedDB.open("notesDB", 1);
request 用來處理資料庫開啟、錯誤,成功和升級等事件。
onerror:資料庫開啟失敗onsuccess:資料庫開啟成功,呼叫 loadNotes() 載入筆記資料。request.onerror = function (event) {
  console.error("資料庫建立失敗", event);
};
request.onsuccess = function (event) {
  db = event.target.result;
  console.log("資料庫建立成功");
  loadNotes();
};
如果是第一次建立資料庫,或是要升級資料庫版本時,都會觸發 onupgradeneeded,我們使用 db.createObjectStore 建立一個資料表,並將 id 設為主鍵,且自動遞增 (autoIncrement: true),此外建立 content 的 index,以便根據筆記內容進行查詢。
request.onupgradeneeded = function (event) {
  db = event.target.result;
  const objectStore = db.createObjectStore("notes", { keyPath: "id", autoIncrement: true });
  objectStore.createIndex("content", "content", { unique: false });
};
⬇️ 建立好的畫面如下

這段跟前面提到的 Create 範例類似,使用 db.transaction 建立一個交易,再用 add() 方法將筆記新增到資料庫中。
function addNote(content) {
  const transaction = db.transaction(["notes"], "readwrite");
  const objectStore = transaction.objectStore("notes");
  const request = objectStore.add({ content: content });
  request.onsuccess = function () {
    loadNotes();
  };
  request.onerror = function (event) {
    console.error("新增筆記失敗", event);
  };
}
這部分跟 Read 的範例類似,因為只要讀取資料,所以該交易的 mode 可用 readonly。另外因為要取得所有的筆記,所以使用 getAll() 方法,不用另外做 index 搜尋。
筆記清單的刪除按鈕,會呼叫 deleteNote() 以刪除筆記,我們下個段落會提到。
function loadNotes() {
  const transaction = db.transaction(["notes"], "readonly");
  const objectStore = transaction.objectStore("notes");
  const request = objectStore.getAll();
  request.onsuccess = function (event) {
    const notes = event.target.result;
    const notesList = document.getElementById("notes-list");
    notesList.innerHTML = "";
    notes.forEach(note => {
      const li = document.createElement("li");
      li.textContent = note.content;
      const deleteButton = document.createElement("button");
      deleteButton.textContent = "刪除";
      deleteButton.onclick = function () {
        deleteNote(note.id);
      };
      li.appendChild(deleteButton);
      notesList.appendChild(li);
    });
  };
  request.onerror = function (event) {
    console.error("載入失敗", event);
  };
}
和 Delete 的範例類似,我們可以刪除指定的筆記,先建立一個 mode 為 readwrite 的交易,再用 delete() 方法,根據筆記的 id 刪除對應的資料。
function deleteNote(id) {
  const transaction = db.transaction(["notes"], "readwrite");
  const objectStore = transaction.objectStore("notes");
  const request = objectStore.delete(id);
  request.onsuccess = function () {
    loadNotes();
  };
  request.onerror = function (event) {
    console.error("刪除失敗", event);
  };
}
以下是完整的程式碼,供各位參考
let db;
const request = window.indexedDB.open("notesDB", 1);
request.onerror = function (event) {
  console.error("資料庫建立失敗", event);
};
request.onsuccess = function (event) {
  db = event.target.result;
  console.log("資料庫建立成功");
  loadNotes();
};
request.onupgradeneeded = function (event) {
  db = event.target.result;
  const objectStore = db.createObjectStore("notes", { keyPath: "id", autoIncrement: true });
  objectStore.createIndex("content", "content", { unique: false });
};
function addNote(content) {
  const transaction = db.transaction(["notes"], "readwrite");
  const objectStore = transaction.objectStore("notes");
  const request = objectStore.add({ content: content });
  request.onsuccess = function () {
    loadNotes();
  };
  request.onerror = function (event) {
    console.error("新增筆記失敗", event);
  };
}
function loadNotes() {
  const transaction = db.transaction(["notes"], "readonly");
  const objectStore = transaction.objectStore("notes");
  const request = objectStore.getAll();
  request.onsuccess = function (event) {
    const notes = event.target.result;
    const notesList = document.getElementById("notes-list");
    notesList.innerHTML = "";
    notes.forEach(note => {
      const li = document.createElement("li");
      li.textContent = note.content;
      const deleteButton = document.createElement("button");
      deleteButton.textContent = "刪除";
      deleteButton.onclick = function () {
        deleteNote(note.id);
      };
      li.appendChild(deleteButton);
      notesList.appendChild(li);
    });
  };
  request.onerror = function (event) {
    console.error("載入失敗", event);
  };
}
function deleteNote(id) {
  const transaction = db.transaction(["notes"], "readwrite");
  const objectStore = transaction.objectStore("notes");
  const request = objectStore.delete(id);
  request.onsuccess = function () {
    loadNotes();
  };
  request.onerror = function (event) {
    console.error("刪除失敗", event);
  };
}
document.getElementById("note-form").onsubmit = function (event) {
  event.preventDefault();
  const content = document.getElementById("note-content").value;
  if (content.trim()) {
    addNote(content);
    document.getElementById("note-content").value = "";
  }
};
這篇文章使用 IndexedDB API 建立了一個簡單的筆記功能,希望能讓大家更了解 IndexedDB API 的好用之處。有任何問題也歡迎留言討論。